iOS Tips #7 画像から動画を作成する | アドカレ2013 : SP #15
かんたんですが、複数枚の画像から動画を作成するロジックを紹介します。以外と紹介されていないので、探されていた方はどうぞ!
この処理に必要なのは、以下の3つのクラスです。
- AVAssetWriter
- AVAssetWriterInput
- AVAssetWriterInputPixelBufferAdaptor
これらのクラスはAVFoundation.frameworkで定義されています。なので、AVFoundation.frameworkをプロジェクトにリンクしておきましょう。
サンプル
それでは早速サンプルを紹介します。画像は以下の5つを使用します。
画像をプロジェクトに追加したら、ViewController.mを以下のように変更してください。
#import "ViewController.h" #import <AVFoundation/AVFoundation.h> @interface ViewController () @property (nonatomic) AVAssetWriter *videoWriter; @end @implementation ViewController // サンプルなのでとりあえずviewDidAppearで実行する - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // 動画にする画像 NSArray *images = @[ [UIImage imageNamed:@"red"], [UIImage imageNamed:@"yellow"], [UIImage imageNamed:@"green"], [UIImage imageNamed:@"blue"], [UIImage imageNamed:@"pink"], ]; // 動画の出力先 NSString *documentPath = (NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]); NSString *moviePath = [documentPath stringByAppendingPathComponent:@"movie.mov"]; [self writeImagesAsMovie:images toPath:moviePath]; } #pragma mark - Movie generator methods /*! 画像の配列から動画を生成する。 @param images 画像の配列 @param path 動画ファイルの出力先 */ - (void)writeImagesAsMovie:(NSArray *)images toPath:(NSString *)path { NSParameterAssert(images); NSParameterAssert(path); NSAssert((images.count > 0), @"Set least one image."); NSFileManager *fileManager = [NSFileManager defaultManager]; // 既にファイルがある場合は削除する if ([fileManager fileExistsAtPath:path]) { [fileManager removeItemAtPath:path error:nil]; } // 最初の画像から動画のサイズ指定する CGSize size = ((UIImage *)images[0]).size; NSError *error = nil; self.videoWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:path] fileType:AVFileTypeQuickTimeMovie error:&error]; if (error) { NSLog(@"%@", [error localizedDescription]); return; } NSDictionary *outputSettings = @{ AVVideoCodecKey : AVVideoCodecH264, AVVideoWidthKey : @(size.width), AVVideoHeightKey : @(size.height), }; AVAssetWriterInput *writerInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:outputSettings]; [self.videoWriter addInput:writerInput]; NSDictionary *sourcePixelBufferAttributes = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32ARGB), (NSString *)kCVPixelBufferWidthKey : @(size.width), (NSString *)kCVPixelBufferHeightKey : @(size.height), }; AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:writerInput sourcePixelBufferAttributes:sourcePixelBufferAttributes]; writerInput.expectsMediaDataInRealTime = YES; // 動画生成開始 if (![self.videoWriter startWriting]) { NSLog(@"Failed to start writing."); return; } [self.videoWriter startSessionAtSourceTime:kCMTimeZero]; CVPixelBufferRef buffer = NULL; int frameCount = 0; int durationForEachImage = 1; int32_t fps = 24; for (UIImage *image in images) { if (adaptor.assetWriterInput.readyForMoreMediaData) { CMTime frameTime = CMTimeMake((int64_t)frameCount * fps * durationForEachImage, fps); buffer = [self pixelBufferFromCGImage:image.CGImage]; if (![adaptor appendPixelBuffer:buffer withPresentationTime:frameTime]) { NSLog(@"Failed to append buffer. [image : %@]", image); } if(buffer) { CVBufferRelease(buffer); } frameCount++; } } // 動画生成終了 [writerInput markAsFinished]; [self.videoWriter finishWritingWithCompletionHandler:^{ NSLog(@"Finish writing!"); }]; CVPixelBufferPoolRelease(adaptor.pixelBufferPool); } - (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image { NSDictionary *options = @{ (NSString *)kCVPixelBufferCGImageCompatibilityKey : @(YES), (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey: @(YES), }; CVPixelBufferRef pxbuffer = NULL; CVPixelBufferCreate(kCFAllocatorDefault, CGImageGetWidth(image), CGImageGetHeight(image), kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef)options, &pxbuffer); CVPixelBufferLockBaseAddress(pxbuffer, 0); void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pxdata, CGImageGetWidth(image), CGImageGetHeight(image), 8, 4 * CGImageGetWidth(image), rgbColorSpace, (CGBitmapInfo)kCGImageAlphaNoneSkipFirst); CGContextConcatCTM(context, CGAffineTransformMakeRotation(0)); CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, CGImageGetHeight(image)); CGContextConcatCTM(context, flipVertical); CGAffineTransform flipHorizontal = CGAffineTransformMake(-1.0, 0.0, 0.0, 1.0, CGImageGetWidth(image), 0.0); CGContextConcatCTM(context, flipHorizontal); CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); CGColorSpaceRelease(rgbColorSpace); CGContextRelease(context); CVPixelBufferUnlockBaseAddress(pxbuffer, 0); return pxbuffer; } @end
これを実行すると、iOS端末のドキュメントディレクトリ(シミュレータの場合は/Users/ユーザ名/Library/Application Support/iPhone Simulator/7.x.x/アプリID/Documents/)にmovie.movが生成されます。これをQuickTimeなどで再生すると以下のように赤>黃>緑>青>ピンクの順にそれぞれ1秒ずつ表示されます。
みなさんお好きなようにいじってみてください!ではまた。